package com.ruoyi.trainingManagement.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.trainingManagement.domain.CourseScheduleDetails;
import com.ruoyi.trainingManagement.domain.CourseStudentInfo;
import com.ruoyi.trainingManagement.domain.Vo.StudentScoreVo;
import com.ruoyi.trainingManagement.mapper.CourseScheduleDetailsMapper;
import com.ruoyi.trainingManagement.mapper.CourseStudentGradeInputMapper;
import com.ruoyi.trainingManagement.service.ICourseStudentGradeInputService;
import jakarta.annotation.Resource;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import org.springframework.web.multipart.MultipartFile;


import java.io.InputStream;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 课程学生成绩录入专用Service实现（支持动态成绩配置 + Excel导入）
 */
@Service
public class CourseStudentGradeInputServiceImpl implements ICourseStudentGradeInputService {

    // 日志对象（固定写法，避免重复创建）
    private static final Logger log = LoggerFactory.getLogger(CourseStudentGradeInputServiceImpl.class);

    // 注入Mapper（统一放在类顶部，符合编码规范）
    @Resource
    private CourseStudentGradeInputMapper gradeInputMapper;
    @Resource
    private CourseScheduleDetailsMapper courseScheduleDetailsMapper;

    // 线程安全存储批量提交失败原因（仅定义1次，避免重复）
    private final ThreadLocal<List<String>> batchFailReasons = ThreadLocal.withInitial(ArrayList::new);


    // ================================== 新增：Excel解析核心方法 ==================================
    /**
     * 解析Excel文件，提取学生成绩数据（实现接口方法，确保权限与接口匹配）
     * @param file 上传的Excel文件（.xlsx格式）
     * @param courseScheduleDetailsId 课程班ID（关联课程配置）
     * @return 解析后的学生成绩列表（CourseStudentInfo）
     * @throws Exception 解析过程中的异常（文件格式错误、数据异常等）
     */
    @Override
    public List<CourseStudentInfo> parseExcelFile(MultipartFile file, String courseScheduleDetailsId) throws Exception {
        List<CourseStudentInfo> studentScoreList = new ArrayList<>();

        // 1. 前置校验：课程班是否存在
        CourseScheduleDetails schedule = courseScheduleDetailsMapper.selectById(courseScheduleDetailsId);
        if (schedule == null) {
            log.error("Excel解析失败：课程班ID[{}]不存在", courseScheduleDetailsId);
            throw new RuntimeException("课程班不存在，请确认课程信息后重试");
        }

        // 2. 读取Excel文件（try-with-resources自动关闭流，避免资源泄漏）
        try (InputStream inputStream = file.getInputStream();
             XSSFWorkbook workbook = new XSSFWorkbook(inputStream)) {

            // 读取第一个工作表（默认使用系统导出的模板，仅第一个表有效）
            Sheet sheet = workbook.getSheetAt(0);
            int totalRows = sheet.getLastRowNum(); // 总行数（从0开始，含表头）
            log.info("开始解析Excel：工作表名称[{}]，总行数[{}]（含表头）", sheet.getSheetName(), totalRows + 1);

            // 3. 校验表头（必须与系统导出模板一致）
            Row headerRow = sheet.getRow(0);
            if (headerRow == null) {
                log.error("Excel解析失败：文件无表头数据");
                throw new RuntimeException("Excel文件无表头，请使用系统导出的「学生成绩表」模板");
            }
            // 预期表头顺序：学号|学生姓名|平时成绩|期末成绩|实验成绩|期中成绩|课程设计成绩|其他成绩
            String[] expectedHeaders = {
                    "学号", "学生姓名", "平时成绩", "期末成绩",
                    "实验成绩", "期中成绩", "课程设计成绩", "其他成绩"
            };
            for (int i = 0; i < expectedHeaders.length; i++) {
                Cell cell = headerRow.getCell(i);
                String actualHeader = (cell == null) ? "" : cell.getStringCellValue().trim();
                if (!expectedHeaders[i].equals(actualHeader)) {
                    log.error("Excel表头错误：第{}列预期为[{}]，实际为[{}]",
                            i + 1, expectedHeaders[i], actualHeader);
                    throw new RuntimeException("Excel表头格式错误，第" + (i + 1) + "列应为：" + expectedHeaders[i]);
                }
            }
            log.info("Excel表头校验通过，开始解析数据行");

            // 4. 解析数据行（从第2行开始，跳过表头）
            for (int rowIdx = 1; rowIdx <= totalRows; rowIdx++) {
                Row dataRow = sheet.getRow(rowIdx);
                if (dataRow == null) {
                    log.warn("Excel解析：跳过空行（行号：{}）", rowIdx + 1);
                    continue;
                }

                // 4.1 构建学生成绩对象（关联课程班ID）
                CourseStudentInfo info = new CourseStudentInfo();
                info.setCourseScheduleDetailsId(courseScheduleDetailsId);

                // 4.2 解析学号（必填，无学号则跳过该条数据）
                Cell studentNoCell = dataRow.getCell(0);
                if (studentNoCell == null || studentNoCell.getStringCellValue().trim().isEmpty()) {
                    log.error("Excel解析：行号[{}]学号为空，跳过该条数据", rowIdx + 1);
                    continue;
                }
                String studentNo = studentNoCell.getStringCellValue().trim();
                // 根据学号查询学生ID（关联数据库中的student_id）
                String studentId = gradeInputMapper.selectStudentIdByNo(studentNo);
                if (studentId == null) {
                    log.error("Excel解析：行号[{}]学号[{}]不存在于系统，跳过该条数据", rowIdx + 1, studentNo);
                    continue;
                }
                info.setStudentId(studentId);

                // 4.3 解析学生姓名（非必填，仅用于日志定位）
                String studentName = (dataRow.getCell(1) == null) ?
                        "" : dataRow.getCell(1).getStringCellValue().trim();

                // 4.4 解析各成绩字段（转为BigDecimal，空值视为0分，超出0-100分视为0分）
                info.setAttendanceScore(parseCellToBigDecimal(dataRow.getCell(2), rowIdx, "平时成绩"));    // 平时成绩→attendanceScore
                info.setExamScore(parseCellToBigDecimal(dataRow.getCell(3), rowIdx, "期末成绩"));          // 期末成绩→examScore
                info.setExperimentScore(parseCellToBigDecimal(dataRow.getCell(4), rowIdx, "实验成绩"));    // 实验成绩→experimentScore
                info.setMidtermScore(parseCellToBigDecimal(dataRow.getCell(5), rowIdx, "期中成绩"));      // 期中成绩→midtermScore
                info.setCourseDesignScore(parseCellToBigDecimal(dataRow.getCell(6), rowIdx, "课程设计成绩"));// 课程设计→courseDesignScore
                info.setOtherScore(parseCellToBigDecimal(dataRow.getCell(7), rowIdx, "其他成绩"));        // 其他成绩→otherScore

                // 4.5 加入结果列表（解析成功）
                studentScoreList.add(info);
                log.info("Excel解析成功：行号[{}]，学号[{}]，姓名[{}]", rowIdx + 1, studentNo, studentName);
            }
        } catch (Exception e) {
            log.error("Excel文件解析异常", e);
            throw new RuntimeException("Excel解析失败：" + e.getMessage());
        }

        // 5. 校验解析结果（无有效数据则抛异常）
        if (studentScoreList.isEmpty()) {
            log.error("Excel解析结果为空：无有效学生成绩数据（可能是空行或学号均不存在）");
            throw new RuntimeException("Excel中无有效成绩数据，请检查文件内容（学号是否存在、是否有成绩）");
        }

        log.info("Excel解析完成：共解析出[{}]条有效学生成绩数据", studentScoreList.size());
        return studentScoreList;
    }

    /**
     * 辅助方法：将Excel单元格值转为BigDecimal（处理空值、格式、范围）
     * @param cell Excel单元格
     * @param rowIdx 行索引（用于日志定位）
     * @param scoreName 成绩名称（如“平时成绩”，用于日志和异常提示）
     * @return 格式化后的成绩（BigDecimal，保留1位小数）
     */
    private BigDecimal parseCellToBigDecimal(Cell cell, int rowIdx, String scoreName) {
        // 空单元格视为0分
        if (cell == null) {
            log.debug("Excel解析：行号[{}]，{}为空，自动填充为0分", rowIdx + 1, scoreName);
            return BigDecimal.ZERO;
        }

        // 强制转为数值类型（避免日期、文本格式导致的解析错误）
        cell.setCellType(CellType.NUMERIC);
        double scoreDouble = cell.getNumericCellValue();
        BigDecimal score = BigDecimal.valueOf(scoreDouble);

        // 成绩范围校验（0-100分，超出范围视为0分）
        if (score.compareTo(BigDecimal.ZERO) < 0 || score.compareTo(new BigDecimal("100")) > 0) {
            log.error("Excel解析：行号[{}]，{}[{}分]超出0-100范围，自动填充为0分",
                    rowIdx + 1, scoreName, score);
            return BigDecimal.ZERO;
        }

        // 保留1位小数（四舍五入），与系统成绩格式保持一致
        return score.setScale(1, BigDecimal.ROUND_HALF_UP);
    }


    // ================================== 原有：成绩查询与提交核心方法 ==================================
    /**
     * 查询课程班下已选课学生（用于成绩录入页面初始化）
     */
    @Override
    public List<StudentScoreVo> selectEnrolledGradeInputStudents(String courseScheduleDetailsId) {
        // 校验课程班是否存在
        CourseScheduleDetails schedule = courseScheduleDetailsMapper.selectById(courseScheduleDetailsId);
        if (ObjectUtils.isEmpty(schedule)) {
            log.error("查询学生成绩失败：课程班ID[{}]不存在", courseScheduleDetailsId);
            throw new RuntimeException("课程班不存在，请刷新页面重试");
        }

        // 调用Mapper查询已选课学生成绩
        List<StudentScoreVo> studentList = gradeInputMapper.selectEnrolledGradeInputStudents(courseScheduleDetailsId);
        log.info("查询课程班[{}]已选课学生：共[{}]人", courseScheduleDetailsId, studentList.size());
        return studentList;
    }

    /**
     * 提交单个学生成绩（手动录入时使用）
     */
    @Override
    public boolean submitGradeInputScore(CourseStudentInfo info) {
        // 1. 校验课程成绩配置（必须先配置成绩组成规则）
        CourseScheduleDetails schedule = courseScheduleDetailsMapper.selectById(info.getCourseScheduleDetailsId());
        if (schedule == null || ObjectUtils.isEmpty(schedule.getScoreConfig())) {
            log.error("提交单个成绩失败：学生[{}]所属课程班[{}]未配置成绩规则",
                    info.getStudentId(), info.getCourseScheduleDetailsId());
            throw new RuntimeException("未配置成绩组成规则，请先在「成绩组成配置」中设置！");
        }

        // 2. 解析成绩配置（JSON→List<Map>）
        List<Map<String, Object>> scoreConfig = parseConfig(schedule.getScoreConfig());

        // 3. 校验成绩字段（仅校验配置中存在的字段，避免无效字段报错）
        validateInfoParams(info, scoreConfig);

        // 4. 动态计算总成绩、绩点、是否及格
        calculateScoreByConfig(info, scoreConfig);

        // 5. 提交到数据库（更新成绩）
        int updateRows = gradeInputMapper.updateGradeInputScore(info);
        boolean success = updateRows > 0;
        log.info("提交单个学生成绩：学生ID[{}]，课程班[{}]，提交结果[{}]（影响行数：{}）",
                info.getStudentId(), info.getCourseScheduleDetailsId(), success ? "成功" : "失败", updateRows);
        return success;
    }

    /**
     * 批量提交学生成绩（手动批量提交/Excel导入后批量入库均使用）
     */
    @Override
    @Transactional(rollbackFor = Exception.class) // 事务保证：要么全成功，要么全回滚（避免部分数据入库）
    public int batchSubmitGradeInputScores(List<CourseStudentInfo> infos) {
        // 清空上一次的失败原因（线程安全，避免多用户数据混淆）
        batchFailReasons.get().clear();
        int successCount = 0;
        int totalCount = infos.size();
        log.info("开始批量提交成绩：共[{}]条学生成绩数据", totalCount);

        // 遍历每条成绩数据，逐条处理（失败不中断，收集失败原因）
        for (CourseStudentInfo info : infos) {
            try {
                // 1. 校验课程配置（每条数据都需校验，避免跨课程班提交）
                CourseScheduleDetails schedule = courseScheduleDetailsMapper.selectById(info.getCourseScheduleDetailsId());
                if (schedule == null || ObjectUtils.isEmpty(schedule.getScoreConfig())) {
                    throw new RuntimeException("课程班未配置成绩规则");
                }

                // 2. 解析配置、校验成绩、计算总成绩
                List<Map<String, Object>> scoreConfig = parseConfig(schedule.getScoreConfig());
                validateInfoParams(info, scoreConfig);
                calculateScoreByConfig(info, scoreConfig);

                // 3. 提交到数据库
                int updateRows = gradeInputMapper.updateGradeInputScore(info);
                if (updateRows > 0) {
                    successCount++;
                    log.debug("批量提交成功：学生ID[{}]，课程班[{}]", info.getStudentId(), info.getCourseScheduleDetailsId());
                } else {
                    throw new RuntimeException("数据库更新失败（无匹配学生或课程班）");
                }
            } catch (Exception e) {
                // 收集失败原因（含学号，便于前端展示）
                String studentNo = gradeInputMapper.selectStudentNoById(info.getStudentId());
                String failReason = "学生" + (ObjectUtils.isEmpty(studentNo) ? info.getStudentId() : studentNo) +
                        "：" + e.getMessage();
                batchFailReasons.get().add(failReason);
                log.error("批量提交失败：{}", failReason, e);
            }
        }

        // 打印批量提交结果日志
        log.info("批量提交成绩完成：总[{}]条，成功[{}]条，失败[{}]条",
                totalCount, successCount, totalCount - successCount);
        return successCount;
    }


    // ================================== 辅助方法：配置解析、成绩计算、字段反射 ==================================
    /**
     * 解析成绩配置JSON（将数据库存储的JSON字符串转为List<Map>）
     * @param configJson 数据库中的成绩配置（如：[{"dbField":"attendanceScore","ratio":30},...]）
     * @return 解析后的配置列表（包含dbField字段名、ratio占比）
     */
    private List<Map<String, Object>> parseConfig(String configJson) {
        List<Map<String, Object>> scoreConfig = new ArrayList<>();
        if (ObjectUtils.isEmpty(configJson)) {
            log.warn("成绩配置为空，返回默认空列表");
            return scoreConfig;
        }

        try {
            // 手动解析JSON数组（避免FastJSON泛型推断报错）
            JSONArray jsonArray = JSON.parseArray(configJson);
            for (Object item : jsonArray) {
                JSONObject jsonObj = (JSONObject) item;
                Map<String, Object> configMap = new HashMap<>();
                // 遍历JSON的所有键值对（确保不遗漏配置字段）
                for (String key : jsonObj.keySet()) {
                    configMap.put(key, jsonObj.get(key));
                }
                scoreConfig.add(configMap);
            }
            log.debug("成绩配置解析成功：共[{}]个成绩项", scoreConfig.size());
        } catch (Exception e) {
            log.error("成绩配置JSON解析失败，配置内容：{}", configJson, e);
            throw new RuntimeException("成绩配置格式错误，请重新设置（如：[{\"dbField\":\"attendanceScore\",\"ratio\":30}]）");
        }
        return scoreConfig;
    }

    /**
     * 校验成绩参数（仅校验配置中存在的字段，避免无效字段干扰）
     * @param info 学生成绩对象
     * @param scoreConfig 成绩配置列表
     */
    private void validateInfoParams(CourseStudentInfo info, List<Map<String, Object>> scoreConfig) {
        // 基础校验：学生ID和课程班ID不能为空
        if (ObjectUtils.isEmpty(info.getStudentId())) {
            throw new RuntimeException("学生ID不能为空");
        }
        if (ObjectUtils.isEmpty(info.getCourseScheduleDetailsId())) {
            throw new RuntimeException("课程班ID不能为空");
        }

        // 按配置校验成绩字段（仅校验配置中存在的dbField）
        for (Map<String, Object> configItem : scoreConfig) {
            String dbField = (String) configItem.get("dbField");
            if (ObjectUtils.isEmpty(dbField)) {
                continue; // 配置中无dbField则跳过
            }

            // 反射获取字段值（处理null）
            BigDecimal score = getFieldValue(info, dbField);
            // 成绩范围校验（0-100分，null视为0分无需校验）
            if (score != null && (score.compareTo(BigDecimal.ZERO) < 0 || score.compareTo(new BigDecimal("100")) > 0)) {
                throw new RuntimeException(dbField + "成绩必须在0-100分之间（当前：" + score + "分）");
            }
        }
    }

    /**
     * 动态计算总成绩和绩点（根据成绩配置的占比）
     * @param info 学生成绩对象（计算后设置totalScore、gradePoint、isPassed）
     * @param scoreConfig 成绩配置列表（含dbField和ratio）
     */
    private void calculateScoreByConfig(CourseStudentInfo info, List<Map<String, Object>> scoreConfig) {
        BigDecimal totalScore = BigDecimal.ZERO;

        // 遍历配置，按占比计算总成绩
        for (Map<String, Object> configItem : scoreConfig) {
            String dbField = (String) configItem.get("dbField");
            Integer ratio = (Integer) configItem.get("ratio");

            // 校验配置有效性（dbField和ratio不能为空，ratio需在1-100之间）
            if (ObjectUtils.isEmpty(dbField) || ratio == null || ratio < 1 || ratio > 100) {
                log.warn("成绩配置无效：dbField[{}]，ratio[{}]，跳过该成绩项", dbField, ratio);
                continue;
            }

            // 反射获取成绩值（null视为0分）
            BigDecimal score = getFieldValue(info, dbField);
            // 累加占比成绩（如：平时成绩80分 * 30% = 24分）
            BigDecimal ratioScore = score.multiply(new BigDecimal(ratio)).divide(new BigDecimal(100), 1, BigDecimal.ROUND_HALF_UP);
            totalScore = totalScore.add(ratioScore);
        }

        // 设置计算结果（总成绩保留1位小数，绩点按规则计算，是否及格按60分划分）
        info.setTotalScore(totalScore.setScale(1, BigDecimal.ROUND_HALF_UP));
        info.setGradePoint(calculateGradePoint(totalScore));
        info.setIsPassed(totalScore.compareTo(new BigDecimal("60")) >= 0 ? 1 : 0);
        log.debug("成绩计算完成：学生[{}]，总成绩[{}]，绩点[{}]，是否及格[{}]",
                info.getStudentId(), info.getTotalScore(), info.getGradePoint(), info.getIsPassed() == 1 ? "是" : "否");
    }

    /**
     * 反射工具：安全获取CourseStudentInfo的字段值（处理null和反射异常）
     * @param info 学生成绩对象
     * @param field 字段名（如：attendanceScore、examScore）
     * @return 字段值（BigDecimal，null返回0）
     */
    private BigDecimal getFieldValue(CourseStudentInfo info, String field) {
        try {
            // 构建getter方法名（如：field=attendanceScore → getter=getAttendanceScore）
            String getterName = "get" + field.substring(0, 1).toUpperCase() + field.substring(1);
            Method getterMethod = CourseStudentInfo.class.getMethod(getterName);
            // 调用getter方法获取值
            Object value = getterMethod.invoke(info);
            return (value == null) ? BigDecimal.ZERO : (BigDecimal) value;
        } catch (Exception e) {
            log.error("反射获取字段值失败：字段[{}]，学生[{}]", field, info.getStudentId(), e);
            throw new RuntimeException("字段[" + field + "]解析失败（可能字段不存在或类型不匹配）");
        }
    }

    /**
     * 计算绩点（按标准规则：90+→4.0，80-89→3.0，70-79→2.0，60-69→1.0，<60→0.0）
     * @param totalScore 总成绩
     * @return 绩点（BigDecimal，保留1位小数）
     */
    private BigDecimal calculateGradePoint(BigDecimal totalScore) {
        if (totalScore.compareTo(new BigDecimal("90")) >= 0) {
            return new BigDecimal("4.0");
        } else if (totalScore.compareTo(new BigDecimal("80")) >= 0) {
            return new BigDecimal("3.0");
        } else if (totalScore.compareTo(new BigDecimal("70")) >= 0) {
            return new BigDecimal("2.0");
        } else if (totalScore.compareTo(new BigDecimal("60")) >= 0) {
            return new BigDecimal("1.0");
        } else {
            return new BigDecimal("0.0");
        }
    }


    // ================================== 新增：学号查询与失败原因获取 ==================================
    /**
     * 根据学号查询学生ID（Excel解析时关联学生）
     * @param studentNo 学号（如：107622025210832）
     * @return 学生ID（如：123456，不存在返回null）
     */
    @Override
    public String getStudentIdByNo(String studentNo) {
        if (ObjectUtils.isEmpty(studentNo)) {
            log.error("根据学号查询学生ID失败：学号为空");
            return null;
        }
        String studentId = gradeInputMapper.selectStudentIdByNo(studentNo);
        log.debug("根据学号查询学生ID：学号[{}] → 学生ID[{}]", studentNo, studentId);
        return studentId;
    }

    /**
     * 获取批量提交的失败原因列表（线程安全，避免多用户数据混淆）
     * @return 失败原因列表（如：["学生107622025210832：课程班未配置成绩规则",...]）
     */
    @Override
    public List<String> getBatchFailReasons() {
        List<String> reasons = batchFailReasons.get();
        log.debug("获取批量提交失败原因：共[{}]条", reasons.size());
        return reasons;
    }
}